package Service;

import java.io.ByteArrayInputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import Engine.MetaScheduleProtocol;
import Engine.engine;
import Exception.ServiceException;
import Expression.Expression;
import Expression.ExpressionValue;
import PSQueue.AbstractPublication;
import Schema.BehaviorInterfaceDefinition;
import Schema.ControlNodeTypeDefinition;
import Schema.ExceptionCatchDefinition;
import Schema.GuardDefinition;
import Schema.OrchestrationDefinition;
import Schema.ProcessDataDefinition;
import Schema.ProcessDefinition;
import Schema.ServiceDefinition;
import Schema.ServiceType;
import Schema.TransitionDefinition;
import Schema.VariableDefinition;
import Schema.VariableType;
import Schema.ControlNodeService;

/**
 * @author cyye
 *
 */

public class Process extends AbstractService {

	@Override
	public void OnNotification(AbstractPublication pub) {
		//handle subscribed events						
		
	}

	@Override
	public void execute(AbstractParameter invocationParameter) {		
        assert invocationParameter!=null;
		try {
    	   //1. Initialize variables
		   initalizeVariables(invocationParameter);
		
		   //2. Get first task
		   String initialService_Name = getInitialService();
		   assert initialService_Name!=null;
		
		   //3. Invoke first task
		   invokeService(this.getPackageName(), initialService_Name);
		}catch(ServiceException e) {
			//return exception to invoker
			
		}
	}
			
	

	public void OnInvocationResult(String invokedServiceName, AbstractParameter returned_parameter) {
		assert invokedServiceName!=null;
		assert returned_parameter!=null;
		
		//1. Update variables
		updateVariables(invokedServiceName, returned_parameter);
		
		//2. Check whether this is the last service
		if(isLastService(invokedServiceName)) {
		    publishCompletionInfo();
		} else {
		//3. Schedule next available services to execute
			Set<String> availableServices = getNextAvailableService(invokedServiceName);
			assert availableServices!=null;
			Iterator<String> it = availableServices.iterator();
			while(it.hasNext()) {
				String serviceName = it.next();
				invokeService(this.getPackageName(), serviceName);
			}
		}
	}			

	private boolean isLastService(String invokedServiceName) {
		assert invokedServiceName!=null;
		
		List<String> final_services = ((ProcessDefinition)m_serviceDefinition).getFinalService();
		assert final_services!=null;
		
		return final_services.contains(invokedServiceName);
	}

	public void OnException(String invokedServiceName, ServiceException thrownException) {
        assert invokedServiceName!=null;
        assert thrownException!=null;
        
        //1. Get exception type
        String exceptionType = ""+thrownException.getExceptionType();//need change the exceptiontype
        
        //2. find the first matched exception handler of invoked service
        String exceptionHandlerName = getFirstMatchedExceptionHandler(invokedServiceName, exceptionType);
        
        //3. invoke the exception handler if find one matched
        if(exceptionHandlerName!=null) {
        	invokeService(this.getPackageName(), exceptionHandlerName);
        } 
        //4. no matched exception handler, propagate the exception to the invoker of this service 
        else {
        	throwException(thrownException);          	
        }
		
	}
	
	private String getFirstMatchedExceptionHandler(String invokedServiceName,
			String exceptionType) {
		// TODO Auto-generated method stub
		//need to make sure the invokedService may be an exception handler too
		//all the exception handlers after this one should be counted
		
		return null;
	}

	private Set<String> getNextAvailableService(String invokedServiceName) {		
		HashSet<String> result = new HashSet<String>();
		
		//should consider the invoked service is an exception handler 
		OrchestrationDefinition orchestration = ((ProcessDefinition)m_serviceDefinition).getOrchestration();
		assert orchestration!=null;
		
		List<TransitionDefinition> transitions = orchestration.getTransition();
		assert transitions!=null;
		
		boolean found = false;
		Iterator<TransitionDefinition> it = transitions.iterator();
		while(it.hasNext()) {
			TransitionDefinition transDef = it.next();
			String source = transDef.getSource();
			assert source!=null;
			if(source.equals(invokedServiceName)) {
			   found = true;
			   String sink = transDef.getSink();
			   assert sink!=null;
			   
			   GuardDefinition guard = transDef.getGuard();
			   assert guard!=null;
			   
			   if(evaluateGuard(guard)) {
				    if(canStartNextService(source, sink)) 
				    	result.add(sink);				    
				    
				    if(!isSplitAnd(invokedServiceName)) return result;
			   }
			}
		}
		
		if(!found) {//check exception handling
			List<ExceptionCatchDefinition> exceptionlink = orchestration.getExceptionCatch();
			assert exceptionlink!=null;
			Iterator<ExceptionCatchDefinition> it_exception = exceptionlink.iterator();
			while(it_exception.hasNext()) {
				ExceptionCatchDefinition exceptionCatchDef = it_exception.next();
				
				String thrownService = exceptionCatchDef.getService();
				assert thrownService!=null;
				
				String handler = exceptionCatchDef.getHandler();
				assert handler!=null;
				
				if(handler.equals(invokedServiceName)) { //resume next service from the service throwing the exception
					return getNextAvailableService(thrownService);
				}
			}
			
		}
		return result;
	}
	
	private boolean isSplitAnd(String invokedServiceName) {
        assert invokedServiceName!=null;
        ServiceDefinition serviceDef = engine.getEngine().getServiceDefinition(this.getPackageName(), invokedServiceName);
        assert serviceDef!=null;
        
        ServiceType serviceType = serviceDef.getType();
        assert serviceType!=null;
        
        if(!serviceType.equals(ServiceType.CONTROL_NODE))return false;
        
        ControlNodeTypeDefinition cNodeType = ((ControlNodeService)serviceDef).getControlType();
        assert cNodeType!=null;
        
        return cNodeType.equals(ControlNodeTypeDefinition.SPLIT_AND);
				
	}

	//save the join_node issue flags
	private HashMap<String, ArrayList<String>> join_nodes = new HashMap<String, ArrayList<String>>();
	private synchronized boolean issueJoinTransition(String source, String sink) {
	   	assert source!=null;
	   	assert sink!=null;
	   	ArrayList<String> prefixs = join_nodes.get(sink);
	   	if(prefixs==null) {
	   		prefixs = initializePrefix(sink);
	   		join_nodes.put(sink, prefixs);
	   	}
	   	
	   	prefixs.remove(source);
	   	if(prefixs.isEmpty()) { //ready to run
	   		//remove the flags
	   		join_nodes.remove(sink);
	   		
	   		return true;
	   	}
	   	
	   	return false;
	}
	
	private ArrayList<String> initializePrefix(String sName) {
		assert sName!=null;
		ArrayList<String> prefixs = new ArrayList<String>();
		
		OrchestrationDefinition orchestration = ((ProcessDefinition)m_serviceDefinition).getOrchestration();
		assert orchestration!=null;
		
		List<TransitionDefinition> transitions = orchestration.getTransition();
		assert transitions!=null;
		
		Iterator<TransitionDefinition> it = transitions.iterator();
		while(it.hasNext()) {
			TransitionDefinition tranDef = it.next();
			
			String source = tranDef.getSource();
			assert source!=null;
			
			String sink = tranDef.getSink();
			assert sink!=null;
			
			if(sink.equals(sName)) {
				if(!prefixs.contains(source)) prefixs.add(source);
			}
		}
		
		return prefixs;
	}

	private boolean canStartNextService(String source,String sink) {
        assert sink!=null;
		ServiceDefinition serviceDef = engine.getEngine().getServiceDefinition(this.getPackageName(), sink);
		assert serviceDef!=null;
		
		ServiceType serviceType = serviceDef.getType();
		assert serviceType!=null;
		
		if(!serviceType.equals(ServiceType.CONTROL_NODE))return true;
		
		ControlNodeTypeDefinition controlType = ((ControlNodeService)serviceDef).getControlType();
		assert controlType!=null;
		
		if(!controlType.equals(ControlNodeTypeDefinition.JOIN_AND)) return true;
		
		//Join_And node, we need to make sure all its incoming transitions are issued				
		return issueJoinTransition(source, sink);
	}

	private boolean evaluateGuard(GuardDefinition guard) {
        assert guard!=null;
        String condition = guard.getCondition();
        assert condition!=null;
        
        ArrayList<Variable> vList = contructVariableList();
        assert vList!=null;
                  
		ByteArrayInputStream in = new ByteArrayInputStream(condition.getBytes());	   
		Expression parser = new Expression(in);
		ExpressionValue value;
		   
		try{
		   value = parser.CalculateExpression(vList);				  			   
		   assert value!=null;
		   assert value.type == Variable.BOOLEAN;		   			   		   			   
		   return (Boolean)value.value;	   
		} catch (Exception e) {
		        System.out.println("NOK.");
		        System.out.println(e);		
		        
		} catch (Error e) {
		        System.out.println("Oops.");
		        System.out.println(e);		
		}	  	  	 
        return false;
	}

	private ArrayList<Variable> contructVariableList() {
		// TODO Auto-generated method stub
		return null;
	}

	private void invokeService(String pkgName, String serviceName) {
		String instanceID = getInstanceID();
		assert instanceID!=null;
		AbstractParameter parameter = constructParameter(serviceName);
		assert parameter!=null;
		AbstractPublication pub = MetaScheduleProtocol.getServiceInvocationProtocl(instanceID, pkgName, serviceName, parameter);
		publish(pub);
	}
	
	private void updateVariables(String invokedServiceName,
			AbstractParameter returnedParameter) {
		// TODO Auto-generated method stub
		
	}
	
	private AbstractParameter constructParameter(String serviceName) {
		// TODO Auto-generated method stub
		return null;
	}

	private String getInitialService() {
		return ((ProcessDefinition)m_serviceDefinition).getInitialService();		
	}

	
/*
 * ******************************************************************************************
 * 	
 *                Process Data Area (local variables, input/output parameters
 * 
 * ******************************************************************************************
 */
	
	//save initialized local variables of process 
	private HashMap<String, Variable> m_processdata = new HashMap<String, Variable>();
	private void initializeLocalVariables() throws ServiceException {
		ProcessDataDefinition pdd = ((ProcessDefinition)m_serviceDefinition).getProcessData();
		assert pdd!=null;
		
		List<VariableDefinition> vList = pdd.getVariable();
		assert vList!=null;
		
		Iterator<VariableDefinition> it = vList.iterator();
		while(it.hasNext()) {
			VariableDefinition vDef = it.next();
			String vName = vDef.getName();
			assert vName!=null;
			
			Variable variable = initializeVariable(vDef);
			if(variable==null) throw new ServiceException("Invalid Local Variable Definition!");
			
			m_processdata.put(vName, variable);			
		}
 	}
	
	private synchronized Variable getLocalVariable(String name) {
		assert name!=null;
		return m_processdata.get(name);
	}
	
	private synchronized void updateLocalVariable(Variable v) throws ServiceException {
		assert v!=null;
		String name = v.getName();
		assert name!=null;
		
		Variable saved_variable = m_processdata.get(name);
		if(saved_variable==null) throw new ServiceException("No such variable!");
		
		if(saved_variable.getType()!= v.getType()) throw new ServiceException("Incompatible variable!");
		
		Object value = v.getValue();
		if(value==null) throw new ServiceException("Invalid variable value!");
		
		saved_variable.setValue(value);		
	}
	
	
	private void initalizeVariables(AbstractParameter invocationParameter) throws ServiceException {		
		initializeInputParameter(invocationParameter);
		initializeLocalVariables();
		initializeOutputParameter();				
	}

/*
 * ******************************************************************************************
 * 	
 *                Process Definition
 * 
 * ******************************************************************************************
 */
		
    public void initialize(ServiceDefinition serviceDef) {
    	assert serviceDef!=null;
    	ServiceType serviceType = serviceDef.getType();
    	assert serviceType!=null;
		assert serviceType.equals(ServiceType.PROCESS);
		
		m_serviceDefinition = serviceDef;
	}
	
	
}
